package emby.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

import emby.err.RuntimeError;
import emby.lang.EmbyNull;
import emby.lang.JavaConstructorWrapper;
import emby.lang.JavaMethodWrapper;

public class ReflectionHelp {

	public static Object getFieldValueOrMethodWrapper(int line, Class<? extends Object> oClass, String attribute, Object o){
		try {
			Field f = oClass.getField(attribute);
			//Field f = getDeclaredField(oClass, attribute);
			try {
				return f.get(o);
			} catch (Exception e){
				e.printStackTrace();
			}
			return EmbyNull.Null;
		} catch (SecurityException e) {
			throw new RuntimeError(line, e.getMessage());
		} catch (NoSuchFieldException e) {
			//Method[] m = oClass.getMethods();
			ArrayList<Method> m  = getAllMethods(oClass);
			ArrayList<Method> lst = new ArrayList<Method>();
			for( int i=0; i<m.size(); i++){
				if( m.get(i).getName().equals(attribute)){
					lst.add(m.get(i));
				}
			}
			if( lst.size()==0){
				throw new RuntimeError(line, oClass.getName()+" has no methods or fields with name: "+attribute);
			}
			Method[] empty = new Method[0];
			return new JavaMethodWrapper(o, lst.toArray(empty));
		}
	}

	public static Object getConstructorWrapper(int line, Class<? extends Object> oClass){
		Constructor[] consts = oClass.getConstructors();
		if(consts.length==0){
			throw new RuntimeError(line, oClass.getName()+" has no valid constructors.");
		}
		return new JavaConstructorWrapper(consts);
	}

	/**
	 * Returns true if each item in arg is a valid instance of the corresponding
	 * class in parameterTypes.
	 * <br>
	 * Primitive classes in parameterTypes will accept a primitive wrapper object
	 * if it is found in args.
	 * @param parameterTypes
	 * @param args
	 * @return See method description
	 */
	public static boolean  argsMatch(Class[] parameterTypes, Object[] args){
		if( args.length != parameterTypes.length){
			return false;
		}
		for(int i = 0; i<parameterTypes.length; i++){
			//System.out.println(parameterTypes[i]+" :: "+args[i].getClass());
			if( parameterTypes[i].isPrimitive()){
				Class prim = primitiveTypeFor(args[i].getClass());
				if( prim!=null){
					if(  parameterTypes[i] != prim){
						return false;
					}
				}else{
					return false;
				}
			}
			else {
				try{
					parameterTypes[i].cast(args[i]);
				}catch(ClassCastException e){
				return false;
				}
			}
		}
		return true;
	}

    public static Class primitiveTypeFor(Class wrapper) {
        if (wrapper == Boolean.class) return Boolean.TYPE;
        if (wrapper == Byte.class) return Byte.TYPE;
        if (wrapper == Character.class) return Character.TYPE;
        if (wrapper == Short.class) return Short.TYPE;
        if (wrapper == Integer.class) return Integer.TYPE;
        if (wrapper == Long.class) return Long.TYPE;
        if (wrapper == Float.class) return Float.TYPE;
        if (wrapper == Double.class) return Double.TYPE;
        if (wrapper == Void.class) return Void.TYPE;
        return null;
    }
    
	/**
	 * Returns all methods, public and private, of class c
	 * @param c
	 * @return
	 */
	public static ArrayList<Method> getAllMethods(Class<?> c){
		ArrayList<Method> methods = new ArrayList<Method>();
		//System.out.println("c: "+c);
		Method[] declared_methods = c.getDeclaredMethods();
			for( Method m : declared_methods) {
				if(! m.isSynthetic()) {
					methods.add(m);
				}
			}
		
		Class<?> superc = c.getSuperclass();
		//System.out.println("superc: "+superc+" "+(superc==null));
		if( superc != null) {
			methods.addAll(getAllMethods(superc));
		}
		return methods;
	}
	
	/**
	 * Returns all fields, public and private, of class c
	 * @param c
	 * @return
	 */
	public static ArrayList<Field> getAllFields(Class<?> c){
		ArrayList<Field> fields = new ArrayList<Field>();
		//System.out.println("c: "+c);
		Field[] declared_fields = c.getDeclaredFields();
			for( Field f : declared_fields) {
				fields.add(f);
			}
		
		Class<?> superc = c.getSuperclass();
		//System.out.println("superc: "+superc+" "+(superc==null));
		if( superc != null) {
			fields.addAll(getAllFields(superc));
		}
		return fields;
	}
	
	/**
	 * Returns the field , regardless of modifier
	 * @param c
	 * @return
	 * @throws NoSuchFieldException 
	 */
	public static Field getDeclaredField(Class<?> c, String fieldname) throws NoSuchFieldException{
		try {
			Field declared_field = c.getDeclaredField(fieldname);
			return declared_field;
		}catch( NoSuchFieldException e) {
			Class<?> superc = c.getSuperclass();
			if( superc == null) {
				throw e;
			}
			return getDeclaredField(superc, fieldname);
		}
	}
	
	public static Class<?> findClass(int line,String classname, ArrayList<String> imports){
		Class<?> myclass= null;
		for(int i=0; i<imports.size(); i++){
			String fullname = null;
			try {
				fullname = imports.get(i)+classname;
				myclass= Class.forName(fullname); //attempt to find class
				break;
			} catch (ClassNotFoundException e) {
				if( i>= imports.size()-1){ //no more imports left to scan
					throw new RuntimeError(line, "Class "+classname+" not found.");	
				}
			}
		}
		return myclass;
	}
}
